package com.whately.core.base.verifycode;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.whately.core.base.config.interceptor.RemoteIPInterceptor;
import com.whately.core.base.constant.WhatelyCoreCacheKeyEnum;
import com.whately.core.base.exception.ServiceError;
import com.whately.core.base.exception.ServiceException;
import com.whately.core.base.model.RedisKeyBuilder;
import com.whately.core.base.utils.MobileUtils;
import com.whately.core.base.constant.VerifyCodeTypeEnum;
import com.whately.core.base.verifycode.util.SmsGenerateResult;
import com.whately.core.base.verifycode.util.WhatelyVerifyCodeUtil;
import com.whately.core.base.vo.req.CheckSmsCodeCorrectReqVo;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * WhatelyVerifyCodeFacade 接口的实现类，作为备用
 * redis 实现参考 WhatelyVerifyCodeFacadeRedisProxy  @Primary
 */
@Service
@Slf4j
public class WhatelyVerifyCodeFacadeGuavaCacheProxy implements WhatelyVerifyCodeFacade {

    @Autowired
    private WhatelyVerifyCodeUtil whatelyVerifyCodeUtil;


    //把具体的code，value缓存起来
    private Cache<String, AtomicInteger> risk_check_cache_1m = CacheBuilder.newBuilder().maximumSize(2000).expireAfterWrite(1, TimeUnit.MINUTES).build();
    private Cache<String, AtomicInteger> risk_check_cache_24h = CacheBuilder.newBuilder().maximumSize(3000).expireAfterWrite(24, TimeUnit.HOURS).build();

    private Cache<String, AtomicInteger> used_img_code_token_cache_4m = CacheBuilder.newBuilder().maximumSize(3000).expireAfterWrite(4, TimeUnit.MINUTES).build();

    private Cache<String,SmsSendModel> cache_send_sms_code_record_3m = CacheBuilder.newBuilder().maximumSize(3000).expireAfterWrite(3, TimeUnit.MINUTES).build();

    //手机验证码校验
    @Override
    public void checkSmsCodeWhenThroughMobile(CheckSmsCodeCorrectReqVo requestVo, VerifyCodeTypeEnum verifyCodeTypeEnum){
        String token = requestVo.getToken();
        String mobile = requestVo.getMobile();
        String verifyCode = requestVo.getVerifyCode();
        if(StringUtils.isBlank(token) ||StringUtils.isBlank(verifyCode)){
            log.error("checkWhenThroughMobile bad request parameter requestVo {}",requestVo);
            throw new ServiceException(ServiceError.REQUEST_PARAMETER_ERROR);
        }

        if (!MobileUtils.isPhone(mobile)) {
            log.error("BaseLoginHelper checkWhenLoginByMobileSmsCode bad request parameter requestVo {}", requestVo);
            throw new ServiceException(ServiceError.REQUEST_PARAMETER_ERROR.getCode(),"手机号格式不正确");
        }

        RedisKeyBuilder rkb = whatelyVerifyCodeUtil.smsCacheToken(verifyCodeTypeEnum,token);

        SmsSendModel model = cache_send_sms_code_record_3m.getIfPresent(rkb.getKey());
        if(null==model){
            log.error("checkWhenThroughMobile  token is expired requestVo {}",requestVo);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证码已失效，请重新获取");
        }

        //输入错误了，统一2分钟后失效时间
        long count = model.getCountRetry().incrementAndGet();

        //验证码输入错误超过三次，则失效，需要重新输入图片验证码，然后重新获取验证码
        if(count>WhatelyVerifyCodeUtil.SMS_TRY_MAX_TIMES){
            cache_send_sms_code_record_3m.invalidate(rkb.getKey());
            log.error("checkWhenThroughMobile  mobile or sms code not match with server requestVo {}",requestVo);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证失败，请重新获取");
        }

        if(!StringUtils.equals(model.getValue(),mobile+verifyCode)){
            log.error("checkWhenThroughMobile  mobile or sms code not match with server requestVo {}",requestVo);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证码错误");
        }
    }

    @Override
    public SmsGenerateResult sendSmsVerifyCode(VerifyCodeTypeEnum verifyCodeType,
                                 String mobile,int expireMinutes){
        SmsGenerateResult generateResult = whatelyVerifyCodeUtil.generateSmsCodeInfo();

        //把mobile+code缓存起来
        SmsSendModel model = new SmsSendModel();
        model.setValue(mobile+generateResult.getSmsCode());
        RedisKeyBuilder rkb = whatelyVerifyCodeUtil.smsCacheToken(verifyCodeType,generateResult.getToken());
        cache_send_sms_code_record_3m.put(rkb.getKey(),model);
        return generateResult;
    }

    @Data
    @ToString
    class SmsSendModel{
        //retry次数
        private AtomicInteger countRetry = new AtomicInteger(0);
        //mobile+code
        private String value;
    }


    //校验验证码，通过解密token方式
    @Override
    public void checkImgVerifyCodeFromAesToken(VerifyCodeTypeEnum verifyCodeType,String token,String riskUniqueId,String imgVerifyCode){
        //检查token是否被使用了
        RedisKeyBuilder usedCkb = WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE_USED.obtainKeyBuilder().appendFixed(token);
        AtomicInteger usedCacheValue = used_img_code_token_cache_4m.getIfPresent(usedCkb.getKey());
        if(usedCacheValue!=null){
            log.error("getSmsVerifyCode verifyCodeType token used ,type {} ,verifyCode {},token {}",verifyCodeType,imgVerifyCode,token);
            throw new ServiceException(ServiceError.VERIFY_CODE_IMG_ERROR.getCode(), "图形验证码已过期");
        }

        //检查token是否正确
        whatelyVerifyCodeUtil.checkImgTokenValue(verifyCodeType,token,riskUniqueId,imgVerifyCode);

        //验证通过后，把用过的token存起来，防止token反复使用
        this.getCacheValue(used_img_code_token_cache_4m,usedCkb,1);
    }


    @Override
    public void checkSmsSendRule(String mobile){
        //一个手机号码，一分钟内只能发送1条
        RedisKeyBuilder key1m = WhatelyCoreCacheKeyEnum.SMS_CODE_SEND_RULE_1m.obtainKeyBuilder().appendVar(mobile);
        int num_1m = this.getCacheValue(risk_check_cache_1m,key1m,WhatelyVerifyCodeUtil.SMS_SEND_COUNT_1m);
        if(num_1m > WhatelyVerifyCodeUtil.SMS_SEND_COUNT_1m){
            log.error("checkImgVerifyCodeFromAes checkSmsSendRule exceed COUNT_IN_1m {} count in 1m mobile {}",WhatelyVerifyCodeUtil.SMS_SEND_COUNT_1m,mobile);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"短信验证码获取过于频繁，请稍后重试");
        }


        //一个手机号码，24小时只能发送30条
        RedisKeyBuilder key24h = WhatelyCoreCacheKeyEnum.SMS_CODE_SEND_RULE_24h.obtainKeyBuilder().appendVar(mobile);
        long num_24h = this.getCacheValue(risk_check_cache_24h,key24h,WhatelyVerifyCodeUtil.SMS_SEND_COUNT_24h);
        if(num_24h > WhatelyVerifyCodeUtil.SMS_SEND_COUNT_24h){
            log.error("checkImgVerifyCodeFromAes checkSmsSendRule exceed  COUNT_IN_12H {} count in 24 hour mobile {}",WhatelyVerifyCodeUtil.SMS_SEND_COUNT_24h,mobile);
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"验证码获取次数已达上限，请24小时后重试");
        }
    }


    @Override
    public boolean riskCheckPass(String riskUniqueId){
        //同一个ip，1分钟超过10次，不通过
        String remoteIP  = RemoteIPInterceptor.getRemoteIP();
        //同一个 id，1分钟超过3次，不通过
        RedisKeyBuilder ipRkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_CHECK_IP.obtainKeyBuilder().appendVar(remoteIP);
        int ipNo = this.getCacheValue(risk_check_cache_1m,ipRkb,WhatelyVerifyCodeUtil.RISK_CHECK_IP_ACCOUNT);

        RedisKeyBuilder idRkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_CHECK_ID.obtainKeyBuilder().appendVar(riskUniqueId);
        int idNo = this.getCacheValue(risk_check_cache_1m,idRkb,WhatelyVerifyCodeUtil.RISK_CHECK_ID_ACCOUNT);


        boolean checkPass ;
        if (ipNo > WhatelyVerifyCodeUtil.RISK_CHECK_IP_ACCOUNT || idNo > WhatelyVerifyCodeUtil.RISK_CHECK_ID_ACCOUNT){
            checkPass = false;
        }else {
            checkPass = true;
        }
        return checkPass;
    }

    @Override
    public int retryTimeInLatestOneMinutes(String riskUniqueId,int maxTryCount){
        RedisKeyBuilder retryRkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_RETRY_ID.obtainKeyBuilder().appendVar(riskUniqueId);
        int tryCount = this.getCacheValue(risk_check_cache_1m,retryRkb,maxTryCount);
        return tryCount;
    }

    @Override
    public void retryTimeClean(String riskUniqueId){
        RedisKeyBuilder retryRkb = WhatelyCoreCacheKeyEnum.RISK_CTRL_RETRY_ID.obtainKeyBuilder().appendVar(riskUniqueId);
        risk_check_cache_1m.invalidate(retryRkb.getKey());
    }

    private int getCacheValue(Cache<String, AtomicInteger> risk_check_cache ,RedisKeyBuilder rkb,int maxCount){
        int value ;
        String key = rkb.getKey();
        AtomicInteger cacheValue = risk_check_cache.getIfPresent(key);

        if(cacheValue==null){
            value = 1;
            cacheValue = new AtomicInteger(value);
            risk_check_cache.put(key,cacheValue);
        }else{
            if(cacheValue.intValue()>=maxCount){
                return maxCount+1;
            }
            value = cacheValue.incrementAndGet();
        }
        return value;
    }
}



